/*
OpenMVL Matrix Vector Library
Copyright (c) 2009 FluidInteractive (R)
 
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising
from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
 
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
 
Written by: Nicola Candussi <nicola@fluidinteractive.com>
*/

//mat_mat_prod.hpp

#ifndef MVL_MAT_MAT_PROD_HPP
#define MVL_MAT_MAT_PROD_HPP

#include "promote_traits.hpp"
#include "expr_order.hpp"
#include "layout_prod.hpp"

namespace mvl {

template<typename E1, typename E2>
struct mat_mat_prod {
    //make sure lhs is row_major and rhs is column_major
    // the result of prod is row_major
    typedef const expr_order<layout::row_major_tag, const E1>       expr1_t;
    typedef const expr_order<layout::column_major_tag, const E2>    expr2_t;
    typedef typename expr1_t::layout_t                              layout1_t;
    typedef typename expr2_t::layout_t                              layout2_t;
    typedef typename expr1_t::const_closure_t                       expr1_closure_t;
    typedef typename expr2_t::const_closure_t                       expr2_closure_t;

    typedef typename promote_traits<
        typename expr1_t::value_type,
        typename expr2_t::value_type
    >::promote_t value_type;

    typedef typename layout::layout_prod<typename expr1_t::layout_t, typename expr2_t::layout_t>::type layout_t;
    typedef typename layout::row_major_tag          order;

    template<typename Entry1, typename Entry2, typename CurrentTag, typename Tag1, typename Tag2>
    struct entry { };

    struct element_dot {
        element_dot(expr1_t const& e1, expr2_t const& e2, value_type &sum): m_e1(e1), m_e2(e2), m_sum(sum) {  }

        template<typename Entry1, typename Entry2, typename CurrentTag>
        void operator()(entry<Entry1, Entry2, CurrentTag, layout::_v_, layout::_v_>) {
            m_sum += m_e1.template at<Entry1>() * m_e2.template at<Entry2>();
        }

        template<typename Entry1, typename Entry2, typename CurrentTag>
        void operator()(entry<Entry1, Entry2, CurrentTag, layout::_1_, layout::_v_>) {
            m_sum += m_e2.template at<Entry2>();
        }

        template<typename Entry1, typename Entry2, typename CurrentTag>
        void operator()(entry<Entry1, Entry2, CurrentTag, layout::_v_, layout::_1_>) {
            m_sum += m_e1.template at<Entry1>();
        }

        template<typename Entry1, typename Entry2, typename CurrentTag>
        void operator()(entry<Entry1, Entry2, CurrentTag, layout::_1_, layout::_1_>) {
            m_sum += value_type(1);
        }

        template<typename Entry1, typename Entry2>
        void operator()(entry<Entry1, Entry2, layout::_0_, layout::_v_, layout::_v_>) {
            m_sum = m_e1.template at<Entry1>() * m_e2.template at<Entry2>();
        }

        template<typename Entry1, typename Entry2>
        void operator()(entry<Entry1, Entry2, layout::_0_, layout::_1_, layout::_v_>) {
            m_sum = m_e2.template at<Entry2>();
        }

        template<typename Entry1, typename Entry2>
        void operator()(entry<Entry1, Entry2, layout::_0_, layout::_v_, layout::_1_>) {
            m_sum = m_e1.template at<Entry1>();
        }

        template<typename Entry1, typename Entry2>
        void operator()(entry<Entry1, Entry2, layout::_0_, layout::_1_, layout::_1_>) {
            m_sum = value_type(1);
        }

        expr1_t const& m_e1;
        expr2_t const& m_e2;
        value_type &m_sum;
    };

    typedef typename layout::begin1<layout1_t>::type l1_begin;
    typedef typename layout::begin1<layout2_t>::type l2_begin;

    template<typename Entry>
    struct dot {
        typedef typename meta::advance<
            l1_begin, 
            typename Entry::i
        >::type l1_it1; 
        typedef typename meta::advance<
            l2_begin, 
            typename Entry::j
        >::type l2_it1; 

        typedef typename layout::begin2<l1_it1>::type begin1;
        typedef typename layout::end2<l1_it1>::type end1;
    
        typedef typename layout::begin2<l2_it1>::type begin2;
        typedef typename layout::end2<l2_it1>::type end2;

        template<typename PrevTag, typename Tag1, typename Tag2> struct compute_tag : 
            layout::add_tags<
                PrevTag,
                typename layout::mul_tags<
                    Tag1,
                    Tag2
                >::type
            > { };

        template<typename Data, typename It1, typename It2, typename CurrentTag, typename Condition>
        struct state {
            typedef Data        data;
            typedef It1         it1;
            typedef It2         it2;
            typedef CurrentTag  current_tag;
            typedef Condition   condition;
            typedef state  type;
        };

        struct update {
        template<typename State>
            struct apply {
                typedef typename meta::deref<typename State::it1>::type e1;
                typedef typename meta::deref<typename State::it2>::type e2;
                typedef typename e1::j j1;
                typedef typename e2::i i2;
                typedef typename meta::eval_if<
                    typename meta::less_equal<j1, i2>::type,
                    meta::next<typename State::it1>,
                    meta::identity<typename State::it1>
                >::type new_it1;
    
                typedef typename meta::eval_if<
                    typename meta::less_equal<i2, j1>::type,
                    meta::next<typename State::it2>,
                    meta::identity<typename State::it2>
                >::type new_it2;

                typedef typename meta::equal_to<j1, i2>::type is_same_pos;

                typedef typename meta::eval_if<
                    is_same_pos,
                    compute_tag<
                        typename State::current_tag,
                        typename e1::tag,
                        typename e2::tag
                    >,
                    meta::identity<typename State::current_tag>
                >::type new_tag;

                typedef typename meta::and_<
                    typename meta::not_equal_to<new_it1, end1>::type,
                    typename meta::not_equal_to<new_it2, end2>::type
                >::type new_condition;

                typedef typename meta::eval_if<
                    is_same_pos,
                    typename meta::push_back<
                        typename State::data,
                        entry<
                            e1,
                            e2,
                            typename State::current_tag,
                            typename e1::tag,
                            typename e2::tag
                        >
                    >,
                    meta::identity<typename State::data>
                >::type new_data;

                typedef state<new_data, new_it1, new_it2, new_tag, new_condition> type;
            };
        };

        typedef typename meta::while_<
            state<
                meta::vector<>,
                begin1,
                begin2,
                layout::_0_,
                typename meta::and_<
                    typename meta::not_equal_to<begin1, end1>::type,
                    typename meta::not_equal_to<begin2, end2>::type
                >::type
            >,
            update
        >::type::data type;
    };

    template<typename Entry>
    static value_type at(expr1_t const& e1, expr2_t const& e2) 
    {
        typedef typename dot<Entry>::type entries;
        value_type sum(0);
        meta::for_each<
            typename meta::begin<entries>::type,
            typename meta::end<entries>::type
        >(element_dot(e1, e2, sum));
        return sum;
    }
};

} // namespace mvl

#endif
